/*
 * Copyright (c) 1989 - 1999 Microsoft Corporation
 */

#include "precomp.h"
#pragma hdrstop

#include "mrxglobs.h"
#include "host_access.h"

static
NTSTATUS
HwFsInitDirInfo(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PMRX_FCB                pFcb,
    IN  PMRX_FOBX               pFobx
)
{
    NTSTATUS                Status;
    PHWFS_FOBX_EXTENSION    pFobxExt = pFobx->Context;
    PMRX_SRV_OPEN           SrvOpen = pFobx->pSrvOpen;
    HWFS_SRVOPEN_EXTENSION* pSrvOpenExt = SrvOpen->Context;

    PHWFS_NETROOT_EXTENSION pNetRootExt = pFcb->pNetRoot->Context;

    pFobxExt->DirInfoSize = 0;
    pFobxExt->CurrFileIndex = 0;

    if (NULL == pFobxExt->Data) {
#pragma warning(disable:4996)
        pFobxExt->Data = ExAllocatePoolWithTag(NonPagedPool, HWFS_FOBX_DIRINFO_SIZE, 'iDwH');
        if (!pFobxExt->Data) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        pFobxExt->DataSize = HWFS_FOBX_DIRINFO_SIZE;

        HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR, ("InitDirInfo; pFobx %p, Ext %p, Data %p\n",
            pFobx, pFobxExt, pFobxExt->Data));
    }

    if (NULL == pFobxExt->ConvertationBuf) {
        pFobxExt->ConvertationBuf = ExAllocatePoolWithTag(NonPagedPool, HWFS_FOBX_CONVERTATION_BUF_SIZE, 'CF');
        if (pFobxExt->ConvertationBuf == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    if (!pFobxExt->WildcardTemplate) {
        // Use lookup to get entry and fill fake direntry
        struct fuse_direntplus* dirent;

        // Buffer supposed to be null terminated
        ASSERT(pFobxExt->DirInfoTemplate.Length < pFobxExt->DirInfoTemplate.MaximumLength);
        ASSERT(pFobxExt->DirInfoTemplate.Buffer[pFobxExt->DirInfoTemplate.Length] == '\0');

        pFobxExt->CurrDirEntryPtr = pFobxExt->Data;
        pFobxExt->NextDirentryOffset = 0;

        // Fill fake direntry
        dirent = (struct fuse_direntplus*)pFobxExt->CurrDirEntryPtr;

        Status = HostLookupEntry(
            pDevExt,
            &pNetRootExt->Share,
            pSrvOpenExt->HwfsFile.FuseNodeId,
            pFobxExt->DirInfoTemplate.Buffer,
            &dirent->entry_out);
        if (!NT_SUCCESS(Status)) {
            return STATUS_NO_MORE_FILES;
        }
        dirent->dirent.ino = dirent->entry_out.nodeid;
        dirent->dirent.namelen = pFobxExt->DirInfoTemplate.Length;
        RtlCopyMemory(dirent->dirent.name, pFobxExt->DirInfoTemplate.Buffer, pFobxExt->DirInfoTemplate.Length + 1);
        dirent->dirent.off = 0;
        dirent->dirent.type = 0;
        pFobxExt->DirInfoSize = FUSE_DIRENTPLUS_SIZE(dirent);

        return STATUS_SUCCESS;
    }

    //
    // FixMe: Windows requires that first entries are "." and ".."
    // except for the root directory. But Fuse doesn't guarantee that.
    //
    Status = HostReadDirectory(
        pDevExt,
        &pSrvOpenExt->HwfsFile,
        0,
        pFobxExt->Data, pFobxExt->DataSize, &pFobxExt->DirInfoSize);

    HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR, ("HostReadDirectory(): status 0x%x, Data %p, Size %u\n",
        Status, pFobxExt->Data, (ULONG)pFobxExt->DirInfoSize));

    if (!NT_SUCCESS(Status)) {
        return Status;
    }
    // DirInfoSize is 0 if all the directory was enumerated
    if (pFobxExt->DirInfoSize == 0) {
        return STATUS_NO_MORE_FILES; // we are first entry in request
    }

    pFobxExt->CurrDirEntryPtr = pFobxExt->Data;
    pFobxExt->NextDirentryOffset = 0;

    return STATUS_SUCCESS;
}

// Ensure that there pFobxExt->CurrDirEntryPtr points to valid dir-entry
// Or we reached end of the directory
static
NTSTATUS
DirInfoEnsureData(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FOBX_EXTENSION    pFobxExt,
    IN  PHWFS_FILE              pHwfsFile)
{
    NTSTATUS        Status;
    PUCHAR          pDirInfoEnd;
    struct fuse_direntplus* dirent;
    LONG            dirent_size;

    Status = STATUS_SUCCESS;
    pDirInfoEnd = pFobxExt->Data + pFobxExt->DirInfoSize;

    if (pFobxExt->CurrDirEntryPtr >= pDirInfoEnd) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR,
            ("Reissue HostReadDirectory(), offset %u\n", (ULONG)pFobxExt->NextDirentryOffset));

        if (!pFobxExt->WildcardTemplate) {
            pFobxExt->DirInfoSize = 0;
            return STATUS_NO_MORE_FILES; // we are first entry in request
        }

        Status = HostReadDirectory(
            pDevExt,
            pHwfsFile,
            pFobxExt->NextDirentryOffset,
            pFobxExt->Data, pFobxExt->DataSize, &pFobxExt->DirInfoSize);

        HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR,
            ("Second HostReadDirectory(): status 0x%x, Data %p, Size %u\n",
                Status, pFobxExt->Data, (ULONG)pFobxExt->DirInfoSize));

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        // HostReadDirectory() returns 0 bytes if all the data was read
        if (pFobxExt->DirInfoSize == 0) {
            return STATUS_NO_MORE_FILES; // we are first entry in request
        }

        pFobxExt->CurrDirEntryPtr = pFobxExt->Data;
        pDirInfoEnd = pFobxExt->Data + pFobxExt->DirInfoSize;
    }

    if (sizeof(*dirent) + pFobxExt->CurrDirEntryPtr > pDirInfoEnd) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_READDIR, ("Invalid data from HostReadDirectory()\n"));
        return STATUS_INVALID_NETWORK_RESPONSE;
    }

    dirent = (struct fuse_direntplus*)pFobxExt->CurrDirEntryPtr;
    dirent_size = FUSE_DIRENTPLUS_SIZE(dirent);
    if (dirent_size + pFobxExt->CurrDirEntryPtr > pDirInfoEnd) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_READDIR, ("Invalid data from HostReadDirectory()\n"));
        return STATUS_INVALID_NETWORK_RESPONSE;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR, ("first dirent in buf inode: %u\n", dirent->dirent.ino));

    return STATUS_SUCCESS;
}

// Moves CurrPtr of DirInfo to the next entry.
// Note: Entry is not guaranteed to be valid.
// Need to call DirInfoEnsureData()
static
VOID
DirInfoAdvanceNextEntry(
    PHWFS_FOBX_EXTENSION    pFobxExt)
{
    struct fuse_direntplus *dirent;
    LONG                    dirent_size;

    dirent = (struct fuse_direntplus*)pFobxExt->CurrDirEntryPtr;
    dirent_size = FUSE_DIRENTPLUS_SIZE(dirent);

    pFobxExt->NextDirentryOffset = dirent->dirent.off;
    pFobxExt->CurrDirEntryPtr += dirent_size;
    pFobxExt->CurrFileIndex++;
}

static
BOOLEAN
FileInfoValidateSize(
    ULONG     InfoEntrySize, // size of info-header
    ULONG     FullEntrySize, // full size with name
    LONG      LengthRemaining,
    PULONG    pPrevEntryOffset,
    NTSTATUS  *pStatus
)
{
    if (LengthRemaining >= (LONG)FullEntrySize) {
        return TRUE; // Continue fill info
    }

    if (pPrevEntryOffset != NULL) {
        // At least one entry was copied. Need return success.
        *pPrevEntryOffset = 0;
        *pStatus = STATUS_SUCCESS;
        return FALSE;
    }

    // Caller must ensure that at least header of entry fits to buffer
    if ((LONG)InfoEntrySize > LengthRemaining) {
        *pStatus = STATUS_INFO_LENGTH_MISMATCH;
        return FALSE;
    }

    // Ok, at least header fits to buffer.
    // Case when fullinfo doesn't fit, will be handled in finalize
    return TRUE;
}

// If Name doesn't fit to buffer, we are guaranteed to be
// first entry copied. Validation was made in VALIDATE_SIZE.
// Note: CopySize can be less then *pLengthRemaining.
// But we nevertheless return OVERFLOW, because we need space
// for FullSize
static
BOOLEAN
FileInfoFinalize(
    ULONG      InfoEntrySize, // size of info-header
    ULONG      FullEntrySize, // full size with name
    ULONG      CopySize,
    ULONG      UnicodeNameSize,
    PLONG      pLengthRemaining,
    PULONG     pPrevEntryOffset,
    PNTSTATUS  pStatus,
    struct fuse_direntplus* dirent,
    PWCHAR     pEntryFileName,
    PULONG     pEntryNextEntryOffset)
{
    ULONG uTmp;

    if (*pLengthRemaining < (LONG)FullEntrySize) {
        ASSERT(pPrevEntryOffset == NULL);
        if ((LONG)CopySize > *pLengthRemaining)
            CopySize = *pLengthRemaining;
        RtlUTF8ToUnicodeN(
            pEntryFileName,
            CopySize - InfoEntrySize,
            &uTmp,
            dirent->dirent.name,
            dirent->dirent.namelen);
        *pLengthRemaining = 0;
        *pEntryNextEntryOffset = 0;
        *pStatus = STATUS_BUFFER_OVERFLOW;
        return FALSE;
    }

    *pLengthRemaining -= FullEntrySize;
    *pEntryNextEntryOffset = FullEntrySize;
    RtlUTF8ToUnicodeN(pEntryFileName, UnicodeNameSize, &uTmp, dirent->dirent.name, dirent->dirent.namelen);

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("%wZ", pEntryFileName));

    return TRUE;
}

static
NTSTATUS
HwFsCopyDirEntry(
    PHWFS_DEVICE_EXTENSION  pDevExt,
    PMRX_FOBX               pFobx,
    FILE_INFORMATION_CLASS  FileInformationClass,
    PULONG                  pPrevEntryOffset, // the field "NextOffset" of the previous Entry
    PUCHAR                  Buffer,
    PLONG                   pLengthRemaining,
    PLONG                   pEntrySize // Size in buffer. Zero if need stop fill.
    )
/*
 * Copy InfoEntry to the Buffer.
 * Zeroes the *pPrevEntryOffset if necessary
 * return *pEntrySize == 0 or NTSTATUS!=0 if should stop copying entries
 */
{
    NTSTATUS                Status;
    PHWFS_FOBX_EXTENSION    pFobxExt = pFobx->Context;
    struct fuse_direntplus* dirent;

    ULONG                   UnicodeNameSize; // size of unicode-name without trail '\0'
    LONG                    CopySize;
    LONG                    FullSize;
    LONG                    InfoEntrySize;
    PMRX_SRV_OPEN           SrvOpen = pFobx->pSrvOpen;
    HWFS_SRVOPEN_EXTENSION* pSrvOpenExt = SrvOpen->Context;

    *pEntrySize = 0;

    // Find next non-empty entry
    while (TRUE) {
        Status = DirInfoEnsureData(pDevExt, pFobxExt, &pSrvOpenExt->HwfsFile);
        if (Status == STATUS_NO_MORE_FILES) {
            if (pPrevEntryOffset == NULL) {
                return STATUS_NO_MORE_FILES; // we are first entry in request
            }
            *pPrevEntryOffset = 0;
            return STATUS_SUCCESS; // reenumeration successfully finished
        }

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        // Note: dirent was validated in DirInfoEnsureData()
        dirent = (struct fuse_direntplus*)pFobxExt->CurrDirEntryPtr;
        UnicodeNameSize = 0;
        Status = RtlUTF8ToUnicodeN(NULL, 0, &UnicodeNameSize, dirent->dirent.name, dirent->dirent.namelen);
        ASSERT(Status == 0 || Status == STATUS_BUFFER_TOO_SMALL);
        if (UnicodeNameSize != 0) {
            break; // Found next non-empty name
        }

        // skip this entry
        DirInfoAdvanceNextEntry(pFobxExt);
    }

    Status = STATUS_SUCCESS;

    // if entry doesn't fit:
    // - return SUCCESS if any previous entry copied to client;
    // - return LENGTH_MISMATCH if size less then Entry
    // otherwise it's ok. We will copy just InfoEntry information
    //
#define FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameLen)  {  \
        CopySize = (InfoEntrySize) + (UnicodeNameLen);  \
        FullSize = (CopySize + 7) & ~7ul;               \
        if (!FileInfoValidateSize(                      \
                (InfoEntrySize), FullSize, *pLengthRemaining, \
                pPrevEntryOffset, &Status)) {           \
            break;                                      \
        }                                               \
    }

    //
    // Note: Time is zero, if not supported by FS
    //
#define FILEINFO_FILL_FTIME(pInfo, dirent)  { \
        struct fuse_attr *a = &(dirent)->entry_out.attr;   \
        (pInfo)->CreationTime.QuadPart = UnixTimeToWindows(a->atime, a->atimensec);              \
        (pInfo)->LastAccessTime.QuadPart = UnixTimeToWindows(a->atime, a->atimensec); \
        (pInfo)->LastWriteTime.QuadPart = UnixTimeToWindows(a->mtime, a->mtimensec);  \
        (pInfo)->ChangeTime.QuadPart = UnixTimeToWindows(a->ctime, a->ctimensec);     \
    }

    // Fills FileIndex and copies FileName
#define FILEINFO_FINALIZE(pInfo, UnicodeNameSize)               \
        (pInfo)->FileIndex = (ULONG)pFobxExt->CurrFileIndex;    \
        (pInfo)->FileNameLength = (UnicodeNameSize);            \
        if (!FileInfoFinalize(InfoEntrySize, FullSize, CopySize, UnicodeNameSize, \
                pLengthRemaining, pPrevEntryOffset, &Status,    \
                dirent, (pInfo)->FileName, &(pInfo)->NextEntryOffset)) { \
            break;                                              \
        }                                                       \
        *pEntrySize = FullSize

    switch (FileInformationClass) {
    case FileDirectoryInformation:
    {
        PFILE_DIRECTORY_INFORMATION pInfo = (PFILE_DIRECTORY_INFORMATION)Buffer;
        InfoEntrySize = FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName);
        FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameSize);

        FILEINFO_FILL_FTIME(pInfo, dirent);
        pInfo->EndOfFile.QuadPart = dirent->entry_out.attr.size;
        pInfo->AllocationSize.QuadPart = ROUND_TO_PAGES(pInfo->EndOfFile.QuadPart);

        pInfo->FileAttributes = PosixAttr2FileAttr(
            &dirent->entry_out.attr, dirent->dirent.name, dirent->dirent.namelen);

        FILEINFO_FINALIZE(pInfo, UnicodeNameSize);
    }
    break;

    case FileFullDirectoryInformation:
    {
        PFILE_FULL_DIR_INFORMATION pInfo = (PFILE_FULL_DIR_INFORMATION)Buffer;
        InfoEntrySize = FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName);
        FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameSize);

        FILEINFO_FILL_FTIME(pInfo, dirent);
        pInfo->EndOfFile.QuadPart = dirent->entry_out.attr.size;
        pInfo->AllocationSize.QuadPart = ROUND_TO_PAGES(pInfo->EndOfFile.QuadPart);

        pInfo->FileAttributes = PosixAttr2FileAttr(
            &dirent->entry_out.attr, dirent->dirent.name, dirent->dirent.namelen);

        FILEINFO_FINALIZE(pInfo, UnicodeNameSize);
    }
    break;

    case FileBothDirectoryInformation:
    {
        PFILE_BOTH_DIR_INFORMATION pInfo = (PFILE_BOTH_DIR_INFORMATION)Buffer;
        InfoEntrySize = FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName);
        FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameSize);

        FILEINFO_FILL_FTIME(pInfo, dirent);

        pInfo->EndOfFile.QuadPart = dirent->entry_out.attr.size;
        pInfo->AllocationSize.QuadPart = ROUND_TO_PAGES(pInfo->EndOfFile.QuadPart);

        pInfo->FileAttributes = PosixAttr2FileAttr(
            &dirent->entry_out.attr, dirent->dirent.name, dirent->dirent.namelen);

        pInfo->ShortNameLength = 0;
        pInfo->ShortName[0] = '\0';

        FILEINFO_FINALIZE(pInfo, UnicodeNameSize);
    }
    break;

    case FileIdBothDirectoryInformation:
    {
        // We need to return some FileId.
        // Windows uses this function to determine whether 2 files
        // in the directory are equal (because of case sensitiveness or short-names).
        PFILE_ID_BOTH_DIR_INFORMATION pInfo = (PFILE_ID_BOTH_DIR_INFORMATION)Buffer;
        InfoEntrySize = FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileName);
        FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameSize);

        FILEINFO_FILL_FTIME(pInfo, dirent);

        pInfo->EndOfFile.QuadPart = dirent->entry_out.attr.size;
        pInfo->AllocationSize.QuadPart = ROUND_TO_PAGES(pInfo->EndOfFile.QuadPart);

        pInfo->FileAttributes = PosixAttr2FileAttr(
            &dirent->entry_out.attr, dirent->dirent.name, dirent->dirent.namelen);
        pInfo->EaSize = 0;
        pInfo->ShortNameLength = 0;
        pInfo->ShortName[0] = '\0';
        // use NodeId as id
        pInfo->FileId.QuadPart = dirent->entry_out.nodeid;

        FILEINFO_FINALIZE(pInfo, UnicodeNameSize);
        break;
    }

    case FileNamesInformation:
    {
        PFILE_NAMES_INFORMATION pInfo = (PFILE_NAMES_INFORMATION)Buffer;
        InfoEntrySize = FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName);
        FILEINFO_VALIDATE_SIZE(pInfo, InfoEntrySize, UnicodeNameSize);

        FILEINFO_FINALIZE(pInfo, UnicodeNameSize);
    }
    break;

    default:
        HWFS_DBG_PRINT(DBG_WARN, DBG_READDIR,
                  ("HwFsQueryDirectory: Unsupported FS information class %u\n",
                  (ULONG)FileInformationClass));
        if (pPrevEntryOffset != NULL)
            *pPrevEntryOffset = 0;
        Status = STATUS_INVALID_PARAMETER;
        break;
    }

    return Status;
}

BOOLEAN
HwFsCheckTemplateMatch(
    IN  PHWFS_FOBX_EXTENSION    pFobxExt,
    IN  struct fuse_direntplus* dirent,
    IN  BOOLEAN ignoreCase
)
{
    BOOLEAN haveMatch = FALSE;
    PWCHAR unicodeBuffer = NULL;

    PUNICODE_STRING pattern = &pFobxExt->UnicodeDirInfoTemplate;

    // quick check for '*;
    if (pattern->Length == sizeof(WCHAR) && pattern->Buffer[0] == L'*') {
        haveMatch = TRUE;
        goto finish;
    }

    if (!ignoreCase) {
        ANSI_STRING name;
        name.Buffer = dirent->dirent.name;
        name.Length = name.MaximumLength = (USHORT)dirent->dirent.namelen;

        PANSI_STRING utfPattern = &pFobxExt->DirInfoTemplate;

        haveMatch = FsRtlIsDbcsInExpression(utfPattern, &name);
        goto finish;
    }

    ULONG requiredUnicodeSize = 0;
    RtlUTF8ToUnicodeN(NULL, 0, &requiredUnicodeSize, dirent->dirent.name, dirent->dirent.namelen);

    unicodeBuffer = pFobxExt->ConvertationBuf;
    ULONG unicodeBufferSize = HWFS_FOBX_CONVERTATION_BUF_SIZE;
    if (requiredUnicodeSize >= unicodeBufferSize) {
        unicodeBuffer = ExAllocatePoolWithTag(NonPagedPool, requiredUnicodeSize + sizeof(WCHAR), 'CTM');
        unicodeBufferSize = requiredUnicodeSize + sizeof(WCHAR);
        if (!unicodeBuffer) {
            goto finish;
        }
    }

    ULONG unicodeBytesWritten = 0;
    NTSTATUS status = RtlUTF8ToUnicodeN(
        unicodeBuffer,
        unicodeBufferSize,
        &unicodeBytesWritten,
        dirent->dirent.name,
        dirent->dirent.namelen
    );
    if (!NT_SUCCESS(status)) {
        goto finish;
    }

    unicodeBuffer[unicodeBytesWritten / sizeof(WCHAR)] = L'\0';

    UNICODE_STRING name;
    name.Buffer = unicodeBuffer;
    name.Length = (USHORT)unicodeBytesWritten;
    name.MaximumLength = (USHORT)unicodeBufferSize;

    haveMatch = FsRtlIsNameInUnUpcasedExpression(pattern, &name, ignoreCase, NULL);
finish:
    if (unicodeBuffer && unicodeBuffer != pFobxExt->ConvertationBuf) {
        ExFreePool(unicodeBuffer);
    }

    return haveMatch;
}

static VOID FobxFreeUTF8Template(
    IN PHWFS_FOBX_EXTENSION    pFobxExt)
{
    if (pFobxExt->DirInfoTemplate.Buffer
            && pFobxExt->DirInfoTemplate.Buffer != pFobxExt->DirInfoSmallTemplateBuf) {
        ExFreePoolWithTag(pFobxExt->DirInfoTemplate.Buffer, 'iDwH');
    }
    pFobxExt->DirInfoTemplate.Buffer = NULL;
    pFobxExt->DirInfoTemplate.Length = pFobxExt->DirInfoTemplate.MaximumLength = 0;
}

static VOID FobxFreeUnicodeTemplate(
    IN PHWFS_FOBX_EXTENSION    pFobxExt)
{
    if (pFobxExt->UnicodeDirInfoTemplate.Buffer
            && (PCHAR)pFobxExt->UnicodeDirInfoTemplate.Buffer != pFobxExt->UnicodeDirInfoSmallTemplateBuf) {
        ExFreePool(pFobxExt->UnicodeDirInfoTemplate.Buffer);
    }
    pFobxExt->UnicodeDirInfoTemplate.Buffer = NULL;
    pFobxExt->UnicodeDirInfoTemplate.Length = pFobxExt->UnicodeDirInfoTemplate.MaximumLength = 0;
}

VOID FobxFreeTemplate(
    IN PHWFS_FOBX_EXTENSION    pFobxExt)
{
    FobxFreeUTF8Template(pFobxExt);
    FobxFreeUnicodeTemplate(pFobxExt);
}

VOID FobxInitTemplateAsWildcard(
    IN PHWFS_FOBX_EXTENSION    pFobxExt)
{
    pFobxExt->WildcardTemplate = TRUE;

    if (pFobxExt->DirInfoTemplate.MaximumLength == 0) {
        pFobxExt->DirInfoTemplate.Buffer = pFobxExt->DirInfoSmallTemplateBuf;
        pFobxExt->DirInfoTemplate.MaximumLength = HWFS_FOBX_SMALLBUF_SIZE;
    }

    pFobxExt->DirInfoTemplate.Length = sizeof(CHAR);
    pFobxExt->DirInfoTemplate.Buffer[0] = '*';
    pFobxExt->DirInfoTemplate.Buffer[1] = '\0'; // null terminated!

    if (pFobxExt->UnicodeDirInfoTemplate.MaximumLength == 0) {
        pFobxExt->UnicodeDirInfoTemplate.Buffer = (PWCHAR)pFobxExt->UnicodeDirInfoSmallTemplateBuf;
        pFobxExt->UnicodeDirInfoTemplate.MaximumLength = HWFS_FOBX_UNICODE_SMALLBUF_SIZE;
    }

    pFobxExt->UnicodeDirInfoTemplate.Length = sizeof(WCHAR);
    pFobxExt->UnicodeDirInfoTemplate.Buffer[0] = L'*';
    pFobxExt->UnicodeDirInfoTemplate.Buffer[1] = L'\0'; // null terminated!
}

static BOOLEAN TemplateIsWildcard(
    IN PUNICODE_STRING         usTemplate)
{
    DECLARE_CONST_UNICODE_STRING(Wildcard1, L"*");
    DECLARE_CONST_UNICODE_STRING(Wildcard2, L"*.*");

    return usTemplate->Length == 0 ||
            RtlEqualUnicodeString(&Wildcard1, usTemplate, TRUE) ||
            RtlEqualUnicodeString(&Wildcard2, usTemplate, TRUE);
}

static NTSTATUS FobxAllocateUTF8Template(
    IN PHWFS_FOBX_EXTENSION    pFobxExt,
    IN PUNICODE_STRING         usTemplate)
{
    FobxFreeUTF8Template(pFobxExt);

    pFobxExt->WildcardTemplate = FsRtlDoesNameContainWildCards(usTemplate);

    ULONG utf8_size = 0;
    NTSTATUS Status = RtlUnicodeToUTF8N(NULL, 0, &utf8_size, usTemplate->Buffer, usTemplate->Length);
    ASSERT(Status == 0 || Status == STATUS_BUFFER_TOO_SMALL);

    PANSI_STRING pAnsi = &pFobxExt->DirInfoTemplate;
    if (utf8_size < HWFS_FOBX_SMALLBUF_SIZE) {
        // Will convert within local buf
        pAnsi->Buffer = pFobxExt->DirInfoSmallTemplateBuf;
        pAnsi->MaximumLength = HWFS_FOBX_SMALLBUF_SIZE;
    } else {
        // Allocate new buf
        pAnsi->Buffer = ExAllocatePoolWithTag(NonPagedPool, utf8_size + 1, 'iDwH');
        if (pAnsi->Buffer == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        pAnsi->MaximumLength = (USHORT)utf8_size + 1;
    }

    pAnsi->Length = (USHORT)utf8_size;
    Status = RtlUnicodeToUTF8N(pAnsi->Buffer, utf8_size, &utf8_size, usTemplate->Buffer, usTemplate->Length);
    ASSERT(Status == 0);

    pAnsi->Buffer[utf8_size] = '\0'; // null terminated!
    pAnsi->Length = (USHORT)utf8_size;

    return STATUS_SUCCESS;
}

static NTSTATUS FobxInitUnicodeTemplate(
    IN PHWFS_FOBX_EXTENSION    pFobxExt,
    IN PUNICODE_STRING         usTemplate,
    IN BOOLEAN                 ignoreCase)
{
    FobxFreeUnicodeTemplate(pFobxExt);

    PUNICODE_STRING pUni = &pFobxExt->UnicodeDirInfoTemplate;
    if (usTemplate->Length < HWFS_FOBX_UNICODE_SMALLBUF_SIZE) {
        pUni->Buffer = (PWCHAR)pFobxExt->UnicodeDirInfoSmallTemplateBuf;
        pUni->Length = 0;
        pUni->MaximumLength = HWFS_FOBX_UNICODE_SMALLBUF_SIZE;
    } else {
        pUni->Buffer = ExAllocatePoolWithTag(NonPagedPool, usTemplate->Length + sizeof(WCHAR), 'UTBA');
        if (pUni->Buffer == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        pUni->Length = 0;
        pUni->MaximumLength = usTemplate->Length + sizeof(WCHAR);
    }

    RtlCopyUnicodeString(pUni, usTemplate);

    if (ignoreCase) {
        NTSTATUS Status = RtlDowncaseUnicodeString(pUni, pUni, FALSE);
        if (!NT_SUCCESS(Status)) {
            return Status;
        }
    }

    return STATUS_SUCCESS;
}

static NTSTATUS FobxAllocateTemplate(
    IN PHWFS_FOBX_EXTENSION    pFobxExt,
    IN PUNICODE_STRING         usTemplate,
    IN BOOLEAN                 ignoreCase)
{
    NTSTATUS Status = STATUS_SUCCESS;

    Status = FobxInitUnicodeTemplate(pFobxExt, usTemplate, ignoreCase);
    if (!NT_SUCCESS(Status)) {
        return Status;
    }

    if (TemplateIsWildcard(usTemplate)) {
        FobxInitTemplateAsWildcard(pFobxExt);
    } else {
        Status = FobxAllocateUTF8Template(pFobxExt, usTemplate);
    }

    return Status;
}

NTSTATUS
HwFsQueryDirectory(
    IN OUT PRX_CONTEXT         RxContext)
{
    NTSTATUS Status = STATUS_INVALID_PARAMETER;
    FILE_INFORMATION_CLASS  FileInformationClass;
    PUINT8                  Buffer;
    PHWFS_DEVICE_EXTENSION  pDevExt;
    PMRX_FOBX               pFobx = RxContext->pFobx;
    PHWFS_FOBX_EXTENSION    pFobxExt = pFobx->Context;
    PMRX_FCB                pFcb = RxContext->pFcb;
    PMRX_SRV_OPEN           SrvOpen = pFobx->pSrvOpen;
    HWFS_SRVOPEN_EXTENSION* pSrvOpenExt = SrvOpen->Context;
    BOOLEAN                 InitialQuery;

    PHWFS_NETROOT_EXTENSION pNetRootExt = pFcb->pNetRoot->Context;
    PHWFS_SHARE pShare = &pNetRootExt->Share;
    BOOLEAN ignoreCase = pShare->VolInfo.IsCaseInsensitive;

    PULONG pPrevEntryOffset = NULL; // the field NextOffset of the previous entry

    RxTraceEnter("HwFsQueryDirectory");

    if (!pSrvOpenExt->HwfsFile.IsValid || !pSrvOpenExt->HwfsFile.IsDir) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_READDIR,
            ("HwFsQueryDirectory: Invalid file handle\n"));
        return STATUS_INVALID_PARAMETER;
    }

    pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);

    // FYI: InitialQuery is TRUE if UnicodeQueryTemplate.Buffer in FOBX is NULL
    // and the Flags member of the FOBX does not have the FOBX_FLAG_MATCH_ALL bit on.
    //
    // For RestartScan the &pFobx->UnicodeQueryTemplate is valid
    //
    // Documentation in MSDN about MRxQueryDirectory looks bullshit:
    // it states that pFobx->UnicodeQueryTemplate is zero for InitialQuery.
    // But this appears to be wrong.
    // The "2.1.5.6.3 Directory Information Queries" says that InitialQuery -
    // it's just first search on that handle. So we can look on whether
    // pFobxExt->DirInfoTemplate.Length is initialized.
    //
    // Note: we allocate own utf8 DirInfoTemplate to avoid conversion
    // of direntry->name to unicode
    InitialQuery = (RxContext->QueryDirectory.InitialQuery || pFobxExt->DirInfoTemplate.Length == 0);

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO,
        ("HwFsQueryDirectory:\n"
            "  RX::Init = %d, Comp::Init %d; Restart: %d; Template %wZ\n",
            RxContext->QueryDirectory.InitialQuery, InitialQuery,
            RxContext->QueryDirectory.RestartScan, &pFobx->UnicodeQueryTemplate));

    if (InitialQuery || RxContext->QueryDirectory.RestartScan) {
        PUNICODE_STRING Template;
        DECLARE_CONST_UNICODE_STRING(Wildcard, L"*");
        Template = pFobx->UnicodeQueryTemplate.Length ?
            &pFobx->UnicodeQueryTemplate : &Wildcard;

        Status = FobxAllocateTemplate(pFobxExt, Template, ignoreCase);
        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        Status = HwFsInitDirInfo(pDevExt, pFcb, pFobx);
        if (!NT_SUCCESS(Status)) {
            goto finish;
        }
        pFobxExt->StartSearchIndex = 0;
    }

    if (pFobxExt->DirInfoSize == 0) {
        Status = STATUS_NO_MORE_FILES;
        goto finish;
    }

    // According to WDK FAT example, specified index has highest priority
    if (RxContext->QueryDirectory.IndexSpecified) {
        pFobxExt->StartSearchIndex = RxContext->QueryDirectory.FileIndex;
    }

    Buffer = RxContext->Info.Buffer;
    while (1) {
        LONG EntrySize;
        struct fuse_direntplus* dirent;
        Status = DirInfoEnsureData(pDevExt, pFobxExt, &pSrvOpenExt->HwfsFile);
        if (Status != STATUS_SUCCESS) {
            if (Status == STATUS_NO_MORE_FILES && pPrevEntryOffset) {
                // since pPrevEntryOffset is not zero, we filled
                // at least one entry in reply
                Status = STATUS_SUCCESS;
            }
            break;
        }

        if (RxContext->QueryDirectory.IndexSpecified &&
                RxContext->QueryDirectory.FileIndex < pFobxExt->CurrFileIndex) {
            DirInfoAdvanceNextEntry(pFobxExt);
            continue;
        }

        // Note: dirent was validated in DirInfoEnsureData()
        dirent = (struct fuse_direntplus*)pFobxExt->CurrDirEntryPtr;
        if (!HwFsCheckTemplateMatch(pFobxExt, dirent, ignoreCase)) {
            DirInfoAdvanceNextEntry(pFobxExt);
            continue;
        }

        Status = HwFsCopyDirEntry(
            pDevExt,
            pFobx,
            RxContext->Info.FileInformationClass,
            pPrevEntryOffset,
            Buffer,
            &RxContext->Info.LengthRemaining,
            &EntrySize);
        if (Status != STATUS_SUCCESS || EntrySize == 0) {
            break;
        }

        DirInfoAdvanceNextEntry(pFobxExt);
        pPrevEntryOffset = (PULONG)Buffer;
        Buffer += EntrySize;

        if (RxContext->QueryDirectory.ReturnSingleEntry) {
            break;
        }
    }

finish:
    if (pPrevEntryOffset) {
        // Make NextOffset in the last InfoEntry be zero
        *pPrevEntryOffset = 0;
    }

    // According to MSDN and WDK Examples, STATUS_NO_SUCH_FILE is returned
    // only for initial query
    if (InitialQuery && Status == STATUS_NO_MORE_FILES) {
        Status = STATUS_NO_SUCH_FILE;
    }

    return Status;
}

static
NTSTATUS
FillFileFsVolumeInformation(
    PVOID       pBuffer,
    PLONG       pLengthRemaining,
    PHWFS_SHARE pShare)
{
    ULONG BytesToCopy;
    PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pBuffer;

    if (*pLengthRemaining < sizeof(FILE_FS_VOLUME_INFORMATION)) {
        return STATUS_INVALID_PARAMETER;
    }

    RtlZeroMemory(pVolInfo, sizeof(FILE_FS_VOLUME_INFORMATION));
    pVolInfo->VolumeCreationTime.QuadPart = 0;
    pVolInfo->VolumeSerialNumber = HWFS_VOLUME_SERIAL;
    pVolInfo->SupportsObjects = FALSE;

    *pLengthRemaining -= FIELD_OFFSET(FILE_FS_VOLUME_INFORMATION, VolumeLabel[0]);
    if (*pLengthRemaining >= (LONG)pShare->ShareName.Length) {
        BytesToCopy = pShare->ShareName.Length;
    } else {
        BytesToCopy = *pLengthRemaining;
    }

    // Use ShareName as volume label
    RtlCopyMemory(&pVolInfo->VolumeLabel[0], (PVOID)pShare->ShareName.Buffer, BytesToCopy);

    *pLengthRemaining -= BytesToCopy;
    pVolInfo->VolumeLabelLength = BytesToCopy;

    return STATUS_SUCCESS;
}

#define HWFS_ALLOC_UNIT     4096
#define HWFS_SECTOR_SIZE    512

static
NTSTATUS
FillFileFsSizeInformation(
    PHWFS_DEVICE_EXTENSION   pDevExt,
    PVOID       pBuffer,
    PLONG       pLengthRemaining,
    PHWFS_SHARE pShare)
{
    HWFS_VOLUME_INFO* vi;
    PFILE_FS_SIZE_INFORMATION pInfo = pBuffer;

    HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsSizeInformation\n"));
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);

    HostUpdateVolumeInfo(pDevExt, pShare, FALSE);
    vi = &pShare->VolInfo;

    pInfo->BytesPerSector = HWFS_SECTOR_SIZE;
    pInfo->SectorsPerAllocationUnit = HWFS_ALLOC_UNIT / HWFS_SECTOR_SIZE; // 4k allocation unit (8*512)
    // Here we have problem. We know total size, total free and free for current user.
    // But we have to fill "Total for current user".
    // Let's fill it as
    // Total - (BytesFree - BytesAvail);
    pInfo->AvailableAllocationUnits.QuadPart = vi->BytesAvail / HWFS_ALLOC_UNIT;
    if (vi->BytesAvail >= vi->BytesFree) {
        // Unexpected case; BytesFree should be >=.
        pInfo->TotalAllocationUnits.QuadPart = vi->BytesTotal / HWFS_ALLOC_UNIT;
    } else {
        pInfo->TotalAllocationUnits.QuadPart =
            (vi->BytesTotal - (vi->BytesFree - vi->BytesAvail)) / HWFS_ALLOC_UNIT;
    }

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileFsFullSizeInformation(
    PHWFS_DEVICE_EXTENSION   pDevExt,
    PVOID       pBuffer,
    PLONG       pLengthRemaining,
    PHWFS_SHARE pShare)
{
    HWFS_VOLUME_INFO* vi;
    PFILE_FS_FULL_SIZE_INFORMATION pInfo = pBuffer;

    HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsFulleSizeInformation\n"));
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);

    HostUpdateVolumeInfo(pDevExt, pShare, FALSE);
    vi = &pShare->VolInfo;

    pInfo->BytesPerSector = HWFS_SECTOR_SIZE;
    pInfo->SectorsPerAllocationUnit = HWFS_ALLOC_UNIT / HWFS_SECTOR_SIZE;
    pInfo->TotalAllocationUnits.QuadPart = vi->BytesTotal / HWFS_ALLOC_UNIT;
    pInfo->CallerAvailableAllocationUnits.QuadPart = vi->BytesAvail / HWFS_ALLOC_UNIT;
    pInfo->ActualAvailableAllocationUnits.QuadPart = vi->BytesFree / HWFS_ALLOC_UNIT;

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileFsDeviceInformation(
    PHWFS_DEVICE_EXTENSION   pDevExt,
    PVOID       pBuffer,
    PLONG       pLengthRemaining,
    PHWFS_SHARE pShare,
    ULONG       RxContextDeviceChars)
{
    PFILE_FS_DEVICE_INFORMATION Info = pBuffer;

    if (*pLengthRemaining < sizeof(*Info)) {
        return STATUS_INVALID_PARAMETER;
    }

    *pLengthRemaining -= sizeof(*Info);

    HostUpdateVolumeInfo(pDevExt, pShare, FALSE);
    Info->DeviceType = FILE_DEVICE_DISK;
    Info->Characteristics = RxContextDeviceChars;
    Info->Characteristics |= FILE_REMOTE_DEVICE; // Must be set
    if (pShare->VolInfo.IsReadOnly) {
        // Not sure it's needed. Nobody does it
        Info->Characteristics |= FILE_READ_ONLY_DEVICE;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("FileFsDeviceInformation: 0x%x 0x%x\n",
        Info->DeviceType, Info->Characteristics));

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileFsAttributeInformation(
    PVOID       pBuffer,
    PLONG       pLengthRemaining,
    PHWFS_SHARE pShare)
{
    LONG BytesToCopy;
    UNICODE_STRING UnicodeStr;
    PFILE_FS_ATTRIBUTE_INFORMATION pAttribInfo = pBuffer;

    if (*pLengthRemaining < sizeof(FILE_FS_ATTRIBUTE_INFORMATION)) {
        return STATUS_INVALID_PARAMETER;
    }

    RtlZeroMemory(pAttribInfo, sizeof(FILE_FS_ATTRIBUTE_INFORMATION));

#define HWFS_MAXIMUM_COMPONENT_NAME_LENGTH 255
    // Maximum name length, num of UTF16 characters
    pAttribInfo->MaximumComponentNameLength = HWFS_MAXIMUM_COMPONENT_NAME_LENGTH;

    pAttribInfo->FileSystemAttributes =
        FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | FILE_UNICODE_ON_DISK;
    if (pShare->VolInfo.IsReadOnly) {
        pAttribInfo->FileSystemAttributes |= FILE_READ_ONLY_VOLUME;
    }

    *pLengthRemaining -= FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName[0]);

    RtlInitUnicodeString(&UnicodeStr, HWFS_FILESYSTEM_NAME);

    BytesToCopy = *pLengthRemaining;
    if (*pLengthRemaining >= UnicodeStr.Length) {
        BytesToCopy = UnicodeStr.Length;
    }

    pAttribInfo->FileSystemNameLength = BytesToCopy;

    RtlCopyMemory(pAttribInfo->FileSystemName, (PVOID)UnicodeStr.Buffer, BytesToCopy);
    *pLengthRemaining -= BytesToCopy;

    return STATUS_SUCCESS;
}

NTSTATUS
HwFsQueryVolumeInformation(
    IN OUT PRX_CONTEXT         RxContext)
{
    NTSTATUS Status = STATUS_INVALID_PARAMETER;

    PHWFS_DEVICE_EXTENSION   pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);

    PMRX_FCB pFcb = RxContext->pFcb;
    PHWFS_NETROOT_EXTENSION pNetRootExt = pFcb->pNetRoot->Context;

    PHWFS_SHARE pShare = &pNetRootExt->Share;

    FS_INFORMATION_CLASS FsInformationClass = RxContext->Info.FsInformationClass;
    PVOID OriginalBuffer = RxContext->Info.Buffer;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO,
        ("FileFsVolumeInformation: %u; BufSize %u\n",
            (ULONG)FsInformationClass, RxContext->Info.LengthRemaining));

    switch (FsInformationClass) {
        case FileFsVolumeInformation:
            Status = FillFileFsVolumeInformation(OriginalBuffer, &RxContext->Info.LengthRemaining, pShare);
            break;

        case FileFsLabelInformation:
            // This appears never called and unused.
            HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsLabelInformation\n"));
            break;

        case FileFsSizeInformation:
            Status = FillFileFsSizeInformation(pDevExt, OriginalBuffer, &RxContext->Info.LengthRemaining, pShare);
            break;

        case FileFsFullSizeInformation:
            Status = FillFileFsFullSizeInformation(pDevExt, OriginalBuffer, &RxContext->Info.LengthRemaining, pShare);
            break;

        case FileFsDeviceInformation:
            Status = FillFileFsDeviceInformation(pDevExt, OriginalBuffer,
                &RxContext->Info.LengthRemaining, pShare, RxContext->RealDevice->Characteristics);
            break;

        case FileFsAttributeInformation:
            Status = FillFileFsAttributeInformation(OriginalBuffer, &RxContext->Info.LengthRemaining, pShare);
            break;

        case FileFsControlInformation:
            HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsControlInformation\n"));
            break;

        case FileFsObjectIdInformation:
            HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsObjectIdInformation\n"));
            break;

        case FileFsMaximumInformation:
            HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("FileFsMaximumInformation\n"));
            break;

        default:
            break;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_TRACE_FUNC,
        ("FileFsVolumeInformation Status -> %08lx\n", Status));

    return Status;
}

NTSTATUS
HwFsSetVolumeInformation(
    IN OUT PRX_CONTEXT         RxContext)
{
    UNREFERENCED_PARAMETER(RxContext);

    HWFS_DBG_PRINT(DBG_ERR, DBG_FILEINFO, ("HwFsSetVolumeInformation not implemented\n"));
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
HwFsIsValidDirectory(
    IN OUT PRX_CONTEXT     RxContext,
    IN     PUNICODE_STRING DirectoryName)
{
    // Is called as part of create or open request processing
    // for the remaining name string beyond the V_NET_ROOT structure
    // if IRP has flag FILE_CREATE_TREE_CONNECTION
    HWFS_DBG_PRINT(DBG_TRACE, DBG_TRACE_FUNC,
        ("HwFsIsValidDirectory '%wZ'\n", DirectoryName));

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileBasicInformation(
    OUT    PVOID pBuffer,
    IN OUT PLONG pLengthRemaining,
    IN     PHWFS_FILE_INFO pFi)
{
    FILE_BASIC_INFORMATION* pInfo = pBuffer;
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);
    RtlCopyMemory(pInfo, &pFi->BasicInfo, sizeof(FILE_BASIC_INFORMATION));

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileInternalInformation(
    OUT    PVOID    pBuffer,
    IN OUT PLONG    pLengthRemaining,
    IN     UINT64   FileIndex)
{
    PFILE_INTERNAL_INFORMATION pInfo = pBuffer;
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);
    pInfo->IndexNumber.QuadPart = FileIndex;
    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileEaInformation(
    OUT    PVOID pBuffer,
    IN OUT PLONG pLengthRemaining)
{
    PFILE_EA_INFORMATION pInfo = pBuffer;
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);
    pInfo->EaSize = 0;
    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileStandardInformation(
    OUT    PVOID pBuffer,
    IN OUT PLONG pLengthRemaining,
    IN     PHWFS_FILE_INFO pFi)
{
    FILE_STANDARD_INFORMATION* pInfo = pBuffer;
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);
    RtlCopyMemory(pInfo, &pFi->StandardInfo, sizeof(FILE_STANDARD_INFORMATION));

    return STATUS_SUCCESS;
}

static
NTSTATUS
FillFileNetworkOpenInformation(
    OUT    PVOID pBuffer,
    IN OUT PLONG pLengthRemaining,
    IN     PHWFS_FILE_INFO pFi)
{
    FILE_NETWORK_OPEN_INFORMATION* pInfo = pBuffer;
    if (*pLengthRemaining < sizeof(*pInfo)) {
        return STATUS_INVALID_PARAMETER;
    }
    *pLengthRemaining -= sizeof(*pInfo);

    pInfo->CreationTime = pFi->BasicInfo.CreationTime;
    pInfo->LastAccessTime = pFi->BasicInfo.LastAccessTime;
    pInfo->LastWriteTime = pFi->BasicInfo.LastWriteTime;
    pInfo->ChangeTime = pFi->BasicInfo.ChangeTime;
    pInfo->AllocationSize = pFi->StandardInfo.AllocationSize;
    pInfo->EndOfFile = pFi->StandardInfo.EndOfFile;
    pInfo->FileAttributes = pFi->BasicInfo.FileAttributes;

    return STATUS_SUCCESS;
}

NTSTATUS
HwFsQueryFileInformation(
    IN PRX_CONTEXT             RxContext)
{
    NTSTATUS Status = STATUS_INVALID_PARAMETER;
    ULONG RemainingLength = RxContext->Info.LengthRemaining;
    __notnull PMRX_FCB pFcb = RxContext->pFcb;
    PHWFS_FCB_EXTENSION pFcbExt = pFcb->Context;
    PHWFS_FILE_INFO pFi = &pFcbExt->FileInfo;

    FILE_INFORMATION_CLASS FunctionalityRequested = RxContext->Info.FileInformationClass;

    RxTraceEnter("HwFsQueryFileInformation");

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO,
        ("HwFsQueryFileInformation, FunctionalityRequested = %d\n", FunctionalityRequested));

    switch(FunctionalityRequested) {
        case FileBasicInformation:
            Status = FillFileBasicInformation(
                RxContext->Info.Buffer, &RxContext->Info.LengthRemaining, pFi);
            break;

        case FileInternalInformation:
            Status = FillFileInternalInformation(
                RxContext->Info.Buffer, &RxContext->Info.LengthRemaining, (UINT64)(ULONG_PTR)pFcb);
            break;

        case FileEaInformation:
            Status = FillFileEaInformation(
                RxContext->Info.Buffer, &RxContext->Info.LengthRemaining);
            break;

        case FileStandardInformation:
            Status = FillFileStandardInformation(
                RxContext->Info.Buffer, &RxContext->Info.LengthRemaining, pFi);
            break;

        case FileNetworkOpenInformation:
            Status = FillFileNetworkOpenInformation(
                RxContext->Info.Buffer, &RxContext->Info.LengthRemaining, pFi);
            break;

        default:
            break;
    }

    RxTraceLeave(Status);
    return(Status);
}

static
NTSTATUS
HwFsRename(
    IN PRX_CONTEXT            RxContext,
    IN PVOID                  pBuffer,
    IN ULONG                  BufferLength)
/*++
    RxContext - the RDBSS context
    pBuffer - pointer to the new name
    bufferlength - and the size
--*/
{
    NTSTATUS Status;
    PFILE_RENAME_INFORMATION pRi;
    PMRX_SRV_OPEN SrvOpen;
    PHWFS_SRVOPEN_EXTENSION pSrvOpenExt;
    PMRX_FCB                pFcb;
    PHWFS_NETROOT_EXTENSION pNetRootExt;

    PHWFS_DEVICE_EXTENSION   pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE, ("FileRename\n"));
    if (BufferLength < sizeof(FILE_RENAME_INFORMATION)) {
        return STATUS_INVALID_PARAMETER;
    }
    pRi = (PFILE_RENAME_INFORMATION)pBuffer;

    ULONG FileNameLen = pRi->FileNameLength;
    if (FileNameLen + FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) > BufferLength || FileNameLen >= 0x10000) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("FileRenameInformation: invalid len\n"));
        return STATUS_INVALID_PARAMETER;
    }

    if (pRi->RootDirectory != 0) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("FileRenameInformation: unexpected RootDir\n"));
        return STATUS_INVALID_PARAMETER;
    }

    SrvOpen = RxContext->pRelevantSrvOpen;
    pSrvOpenExt = SrvOpen->Context;
    pFcb = RxContext->pFcb;
    pNetRootExt = pFcb->pNetRoot->Context;

    UNICODE_STRING NewName;
    NewName.Length = NewName.MaximumLength = (USHORT)pRi->FileNameLength;
    NewName.Buffer = pRi->FileName;

    Status = HostRenameFile(pDevExt, &pNetRootExt->Share, &pSrvOpenExt->HwfsFile, &NewName, pRi->ReplaceIfExists);

    return Status;
}

static
BOOLEAN
HwFsIsDirectoryEmpty(
    IN PHWFS_DEVICE_EXTENSION   pDevExt,
    OUT PHWFS_FILE              pHwfsFile
    )
{
    NTSTATUS Status;
    PUCHAR   pReadDirBuf;
    PUCHAR   pDirInfoEnd;
    PUCHAR   pCurrEntry;
    UINT64   Size;
    BOOLEAN  IsDirEmpty;

#define IS_DIR_EMPTY_BUF 1024

    pReadDirBuf = ExAllocatePoolWithTag(NonPagedPool, IS_DIR_EMPTY_BUF, 'iDwH');
    if (!pReadDirBuf) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("HwFsIsDirectoryEmpty: alloc failed\n"));
        return TRUE;
    }

    IsDirEmpty = TRUE;
    Size = 0;
    Status = HostReadDirectory(
        pDevExt,
        pHwfsFile,
        0,
        pReadDirBuf, IS_DIR_EMPTY_BUF, &Size);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_FWRITE, ("HwFsIsDirectoryEmpty: HostReadDirectory() status 0x%x\n",
            Status));
        goto Finish;
    }

    if (Size == 0) {
        goto Finish; // dir empty
    }

    pDirInfoEnd = pReadDirBuf + Size;
    pCurrEntry = pReadDirBuf;
    while (pCurrEntry < pDirInfoEnd) {
        SIZE_T MaxSize = pDirInfoEnd - pCurrEntry;

        struct fuse_direntplus* dirent = (struct fuse_direntplus*)pCurrEntry;
        if (MaxSize < sizeof(*dirent) || MaxSize < FUSE_DIRENTPLUS_SIZE(dirent)) {
            HWFS_DBG_PRINT(DBG_ERR, DBG_READDIR, ("IsDirEmpty: invalid dirent size\n"));
            break;
        }

#define DOTDOT_NAME_SIZE (sizeof("..") - 1)

        if (dirent->dirent.namelen > DOTDOT_NAME_SIZE) {
            IsDirEmpty = FALSE;
            break;
        }

        if (dirent->dirent.namelen != 0) {
            if (dirent->dirent.namelen != RtlCompareMemory(dirent->dirent.name, "..", dirent->dirent.namelen)) {
                IsDirEmpty = FALSE;
                break;
            }
        }

        pCurrEntry += FUSE_DIRENTPLUS_SIZE(dirent);
    }

Finish:
    ExFreePoolWithTag(pReadDirBuf, 'iDwH');
    return IsDirEmpty;
}

NTSTATUS
HwFsSetFileInformation(
    IN PRX_CONTEXT             RxContext)
{
    NTSTATUS Status = STATUS_INVALID_PARAMETER;
    __notnull PHWFS_DEVICE_EXTENSION   pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);
    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    __notnull PMRX_FOBX pFobx = RxContext->pFobx;
    __notnull PMRX_SRV_OPEN pSrvOpen = pFobx->pSrvOpen;
    PHWFS_SRVOPEN_EXTENSION pSrvOpenExt = pSrvOpen->Context;

    PHWFS_FCB_EXTENSION pFcbExt = Fcb->Context;
    PHWFS_FILE_INFO pFi = &pFcbExt->FileInfo;

    ULONG BufferLength = RxContext->Info.Length;
    FILE_INFORMATION_CLASS FunctionalityRequested = RxContext->Info.FileInformationClass;
    PFILE_END_OF_FILE_INFORMATION pEndOfFileInfo = (PFILE_END_OF_FILE_INFORMATION)RxContext->Info.Buffer;
    LARGE_INTEGER NewAllocationSize;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_TRACE_FUNC, ("Enterting HwFsSetFileInformation\n"));

    switch (FunctionalityRequested) {
        case FileBasicInformation:

            HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("FileBasicInformation\n"));

            if (BufferLength < sizeof(FILE_BASIC_INFORMATION)) {
                break;
            } else {
                PFILE_BASIC_INFORMATION bi = (PFILE_BASIC_INFORMATION)RxContext->Info.Buffer;

                Status = HostSetBasicFileAttributes(
                    pDevExt,
                    &pSrvOpenExt->HwfsFile,
                    bi->FileAttributes,
                    bi->LastAccessTime.QuadPart,
                    bi->LastWriteTime.QuadPart,
                    bi->ChangeTime.QuadPart,
                    &pFcbExt->FileInfo);
            }
            break;

        case FileDispositionInformation:

            HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("FileDispositionInformation\n"));

            if (BufferLength < sizeof(FILE_DISPOSITION_INFORMATION)) {
                break;
            } else {
                FILE_DISPOSITION_INFORMATION* di = (FILE_DISPOSITION_INFORMATION*)RxContext->Info.Buffer;

                if (pSrvOpenExt->HwfsFile.IsDir && di->DeleteFile &&
                        !HwFsIsDirectoryEmpty(pDevExt, &pSrvOpenExt->HwfsFile)) {
                    Status = STATUS_DIRECTORY_NOT_EMPTY;
                    break;
                }
                pSrvOpenExt->HwfsFile.DeleteOnClose = di->DeleteFile;
                pFi->StandardInfo.DeletePending = di->DeleteFile;
            }

            Status = STATUS_SUCCESS;
            break;

        case FilePositionInformation:

            HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("FilePositionInformation\n"));

            if (BufferLength < sizeof(FILE_POSITION_INFORMATION)) {
                break;
            }
            Status = STATUS_SUCCESS;
            break;

        case FileAllocationInformation:

            HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO,
                ("FileAllocationInformation: Size 0x%I64x\n", pEndOfFileInfo->EndOfFile.QuadPart));

            if (BufferLength < sizeof(FILE_ALLOCATION_INFORMATION)) {
                break;
            }

            Status = STATUS_SUCCESS;

            //
            // Windows forbids EOF be beyond AllocationSize.
            // So, if we see the case, we must change file size.
            // btw, this will also automatically change alloc-size on Linux host.
            //
            // Note also: when CreateFile() is called with TRUNCATE_EXISTING disposition.
            // kernel.dll calls us with OPEN_EXISTING option and then call SetAllocationSize.
            if (pEndOfFileInfo->EndOfFile.QuadPart < pFi->StandardInfo.EndOfFile.QuadPart) {
                Status = HwFsSetFileSize(pDevExt, &pSrvOpenExt->HwfsFile, pEndOfFileInfo->EndOfFile.QuadPart, pFi);
            } else if (pEndOfFileInfo->EndOfFile.QuadPart > pFi->StandardInfo.AllocationSize.QuadPart) {
                // Ignore return status of SetAllocSize(). It's not important for us.
                HwFsSetAllocSize(pDevExt, &pSrvOpenExt->HwfsFile, pEndOfFileInfo->EndOfFile.QuadPart, pFi);
                pFi->StandardInfo.AllocationSize.QuadPart = ROUND_TO_PAGES(pEndOfFileInfo->EndOfFile.QuadPart);
            }
            if (NT_SUCCESS(Status)) {
                RxContext->Info.LengthRemaining -= sizeof(FILE_ALLOCATION_INFORMATION);
            }
            break;

        case FileEndOfFileInformation:

            HWFS_DBG_PRINT(DBG_TRACE, DBG_FILEINFO, ("FCB::AllocationSize is 0x%I64x\n",
                Fcb->Header.AllocationSize.QuadPart));

            if (BufferLength < sizeof(FILE_END_OF_FILE_INFORMATION)) {
                break;
            }

            Status = HwFsSetFileSize(pDevExt, &pSrvOpenExt->HwfsFile, pEndOfFileInfo->EndOfFile.QuadPart, pFi);
            if (NT_SUCCESS(Status)) {
                Fcb->Header.AllocationSize.QuadPart = pFi->StandardInfo.AllocationSize.QuadPart;
                RxContext->Info.LengthRemaining -= sizeof(FILE_END_OF_FILE_INFORMATION);
            }

            break;

        case FileRenameInformation:
            Status = HwFsRename(RxContext, RxContext->Info.Buffer, BufferLength);
            break;

        default:
            break;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_TRACE_FUNC,
        ("HwFsSetFileInformation Status -> %08lx\n", Status));
    return Status;
}

NTSTATUS
HwFsSetFileInformationAtCleanup(
    IN PRX_CONTEXT             RxContext)
{
    UNREFERENCED_PARAMETER(RxContext);
    NTSTATUS Status = STATUS_NOT_IMPLEMENTED;

    return(Status);
}
